// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace System.IO
{
    // This abstract base class represents a writer that can write a sequential
    // stream of characters. A subclass must minimally implement the
    // Write(char) method.
    //
    // This class is intended for character output, not bytes.
    // There are methods on the Stream class for writing bytes.
    public abstract partial class TextWriter : MarshalByRefObject, IDisposable, IAsyncDisposable
    {
        public static readonly TextWriter Null = new NullTextWriter();

        // We don't want to allocate on every TextWriter creation, so cache the char array.
        private static readonly char[] s_coreNewLine = Environment.NewLineConst.ToCharArray();

        // s_coreNewLine will be ['\r', '\n'] on Windows and ['\n'] on Unix. This is the opposite, lazily created/cached.
        private static char[]? s_otherCoreNewLine;

        /// <summary>
        /// This is the 'NewLine' property expressed as a char[].
        /// It is exposed to subclasses as a protected field for read-only
        /// purposes.  You should only modify it by using the 'NewLine' property.
        /// In particular you should never modify the elements of the array
        /// as they are shared among many instances of TextWriter.
        /// </summary>
        protected char[] CoreNewLine = s_coreNewLine;
        private string CoreNewLineStr = Environment.NewLineConst;

        // Can be null - if so, ask for the Thread's CurrentCulture every time.
        private readonly IFormatProvider? _internalFormatProvider;

        protected TextWriter()
        {
        }

        protected TextWriter(IFormatProvider? formatProvider)
        {
            _internalFormatProvider = formatProvider;
        }

        public virtual IFormatProvider FormatProvider
            => _internalFormatProvider ?? CultureInfo.CurrentCulture;

        public virtual void Close()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual ValueTask DisposeAsync()
        {
            try
            {
                Dispose();
                return default;
            }
            catch (Exception exc)
            {
                return ValueTask.FromException(exc);
            }
        }

        // Clears all buffers for this TextWriter and causes any buffered data to be
        // written to the underlying device. This default method is empty, but
        // descendant classes can override the method to provide the appropriate
        // functionality.
        public virtual void Flush()
        {
        }

        public abstract Encoding Encoding
        {
            get;
        }

        /// <summary>
        /// Returns the line terminator string used by this TextWriter. The default line
        /// terminator string is Environment.NewLine, which is platform specific.
        /// On Windows this is a carriage return followed by a line feed ("\r\n").
        /// On OSX and Linux this is a line feed ("\n").
        /// </summary>
        /// <remarks>
        /// The line terminator string is written to the text stream whenever one of the
        /// WriteLine methods are called. In order for text written by
        /// the TextWriter to be readable by a TextReader, only one of the following line
        /// terminator strings should be used: "\r", "\n", or "\r\n".
        /// </remarks>
        [AllowNull]
        public virtual string NewLine
        {
            get => CoreNewLineStr;
            set
            {
                value ??= Environment.NewLineConst;

                if (CoreNewLineStr != value)
                {
                    CoreNewLineStr = value;
                    CoreNewLine =
                        value == Environment.NewLineConst ? s_coreNewLine : // current OS default
                        Environment.NewLineConst == "\r\n" && value == "\n" ? (s_otherCoreNewLine ??= ['\n']) : // other OS default
                        Environment.NewLineConst == "\n" && value == "\r\n" ? (s_otherCoreNewLine ??= ['\r', '\n']) : // other OS default
                        value.ToCharArray(); // unknown
                }

                Debug.Assert(CoreNewLineStr == new string(CoreNewLine));
            }
        }

        // Writes a character to the text stream. This default method is empty,
        // but descendant classes can override the method to provide the
        // appropriate functionality.
        //
        public virtual void Write(char value)
        {
        }

        /// <summary>
        /// Writes a rune to the text stream.
        /// </summary>
        /// <param name="value">The rune to write to the text stream.</param>
        public virtual void Write(Rune value)
        {
            // Convert value to span
            ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);

            // Write span
            Write(valueChars[0]);
            if (valueChars.Length > 1)
            {
                Write(valueChars[1]);
            }
        }

        // Writes a character array to the text stream. This default method calls
        // Write(char) for each of the characters in the character array.
        // If the character array is null, nothing is written.
        //
        public virtual void Write(char[]? buffer)
        {
            if (buffer != null)
            {
                Write(buffer, 0, buffer.Length);
            }
        }

        // Writes a range of a character array to the text stream. This method will
        // write count characters of data into this TextWriter from the
        // buffer character array starting at position index.
        //
        public virtual void Write(char[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);

            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (buffer.Length - index < count)
            {
                throw new ArgumentException(SR.Argument_InvalidOffLen);
            }

            for (int i = 0; i < count; i++) Write(buffer[index + i]);
        }

        // Writes a span of characters to the text stream.
        //
        public virtual void Write(ReadOnlySpan<char> buffer)
        {
            char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);

            try
            {
                buffer.CopyTo(new Span<char>(array));
                Write(array, 0, buffer.Length);
            }
            finally
            {
                ArrayPool<char>.Shared.Return(array);
            }
        }

        // Writes the text representation of a boolean to the text stream. This
        // method outputs either bool.TrueString or bool.FalseString.
        //
        public virtual void Write(bool value)
        {
            Write(value ? "True" : "False");
        }

        // Writes the text representation of an integer to the text stream. The
        // text representation of the given value is produced by calling the
        // int.ToString() method.
        //
        public virtual void Write(int value)
        {
            Write(value.ToString(FormatProvider));
        }

        // Writes the text representation of an integer to the text stream. The
        // text representation of the given value is produced by calling the
        // uint.ToString() method.
        //
        [CLSCompliant(false)]
        public virtual void Write(uint value)
        {
            Write(value.ToString(FormatProvider));
        }

        // Writes the text representation of a long to the text stream. The
        // text representation of the given value is produced by calling the
        // long.ToString() method.
        //
        public virtual void Write(long value)
        {
            Write(value.ToString(FormatProvider));
        }

        // Writes the text representation of an unsigned long to the text
        // stream. The text representation of the given value is produced
        // by calling the ulong.ToString() method.
        //
        [CLSCompliant(false)]
        public virtual void Write(ulong value)
        {
            Write(value.ToString(FormatProvider));
        }

        // Writes the text representation of a float to the text stream. The
        // text representation of the given value is produced by calling the
        // float.ToString(float) method.
        //
        public virtual void Write(float value)
        {
            Write(value.ToString(FormatProvider));
        }

        // Writes the text representation of a double to the text stream. The
        // text representation of the given value is produced by calling the
        // double.ToString(double) method.
        //
        public virtual void Write(double value)
        {
            Write(value.ToString(FormatProvider));
        }

        public virtual void Write(decimal value)
        {
            Write(value.ToString(FormatProvider));
        }

        // Writes a string to the text stream. If the given string is null, nothing
        // is written to the text stream.
        //
        public virtual void Write(string? value)
        {
            if (value != null)
            {
                Write(value.ToCharArray());
            }
        }

        // Writes the text representation of an object to the text stream. If the
        // given object is null, nothing is written to the text stream.
        // Otherwise, the object's ToString method is called to produce the
        // string representation, and the resulting string is then written to the
        // output stream.
        //
        public virtual void Write(object? value)
        {
            if (value != null)
            {
                if (value is IFormattable f)
                {
                    Write(f.ToString(null, FormatProvider));
                }
                else
                    Write(value.ToString());
            }
        }

        /// <summary>
        /// Equivalent to Write(stringBuilder.ToString()) however it uses the
        /// StringBuilder.GetChunks() method to avoid creating the intermediate string
        /// </summary>
        /// <param name="value">The string (as a StringBuilder) to write to the stream</param>
        public virtual void Write(StringBuilder? value)
        {
            if (value != null)
            {
                foreach (ReadOnlyMemory<char> chunk in value.GetChunks())
                    Write(chunk.Span);
            }
        }

        // Writes out a formatted string.  Uses the same semantics as
        // string.Format.
        //
        public virtual void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0)
        {
            Write(string.Format(FormatProvider, format, arg0));
        }

        // Writes out a formatted string.  Uses the same semantics as
        // string.Format.
        //
        public virtual void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1)
        {
            Write(string.Format(FormatProvider, format, arg0, arg1));
        }

        // Writes out a formatted string.  Uses the same semantics as
        // string.Format.
        //
        public virtual void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2)
        {
            Write(string.Format(FormatProvider, format, arg0, arg1, arg2));
        }

        // Writes out a formatted string.  Uses the same semantics as
        // string.Format.
        //
        public virtual void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg)
        {
            Write(string.Format(FormatProvider, format, arg));
        }

        /// <summary>
        /// Writes a formatted string to the text stream, using the same semantics as <see cref="string.Format(string, ReadOnlySpan{object?})"/>.
        /// </summary>
        /// <param name="format">A composite format string.</param>
        /// <param name="arg">An object span that contains zero or more objects to format and write.</param>
        public virtual void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg)
        {
            Write(string.Format(FormatProvider, format, arg));
        }

        // Writes a line terminator to the text stream. The default line terminator
        // is Environment.NewLine, but this value can be changed by setting the NewLine property.
        //
        public virtual void WriteLine()
        {
            Write(CoreNewLine);
        }

        // Writes a character followed by a line terminator to the text stream.
        //
        public virtual void WriteLine(char value)
        {
            Write(value);
            WriteLine();
        }

        /// <summary>
        /// Writes a rune followed by a line terminator to the text stream.
        /// </summary>
        /// <param name="value">The rune to write to the text stream.</param>
        public virtual void WriteLine(Rune value)
        {
            // Convert value to span
            ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);

            if (valueChars.Length > 1)
            {
                Write(valueChars[0]);
                WriteLine(valueChars[1]);
            }
            else
            {
                WriteLine(valueChars[0]);
            }
        }

        // Writes an array of characters followed by a line terminator to the text
        // stream.
        //
        public virtual void WriteLine(char[]? buffer)
        {
            Write(buffer);
            WriteLine();
        }

        // Writes a range of a character array followed by a line terminator to the
        // text stream.
        //
        public virtual void WriteLine(char[] buffer, int index, int count)
        {
            Write(buffer, index, count);
            WriteLine();
        }

        public virtual void WriteLine(ReadOnlySpan<char> buffer)
        {
            char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);

            try
            {
                buffer.CopyTo(new Span<char>(array));
                WriteLine(array, 0, buffer.Length);
            }
            finally
            {
                ArrayPool<char>.Shared.Return(array);
            }
        }

        // Writes the text representation of a boolean followed by a line
        // terminator to the text stream.
        //
        public virtual void WriteLine(bool value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of an integer followed by a line
        // terminator to the text stream.
        //
        public virtual void WriteLine(int value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of an unsigned integer followed by
        // a line terminator to the text stream.
        //
        [CLSCompliant(false)]
        public virtual void WriteLine(uint value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of a long followed by a line terminator
        // to the text stream.
        //
        public virtual void WriteLine(long value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of an unsigned long followed by
        // a line terminator to the text stream.
        //
        [CLSCompliant(false)]
        public virtual void WriteLine(ulong value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of a float followed by a line terminator
        // to the text stream.
        //
        public virtual void WriteLine(float value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of a double followed by a line terminator
        // to the text stream.
        //
        public virtual void WriteLine(double value)
        {
            Write(value);
            WriteLine();
        }

        public virtual void WriteLine(decimal value)
        {
            Write(value);
            WriteLine();
        }

        // Writes a string followed by a line terminator to the text stream.
        //
        public virtual void WriteLine(string? value)
        {
            if (value != null)
            {
                Write(value);
            }
            Write(CoreNewLineStr);
        }

        /// <summary>
        /// Equivalent to WriteLine(stringBuilder.ToString()) however it uses the
        /// StringBuilder.GetChunks() method to avoid creating the intermediate string
        /// </summary>
        public virtual void WriteLine(StringBuilder? value)
        {
            Write(value);
            WriteLine();
        }

        // Writes the text representation of an object followed by a line
        // terminator to the text stream.
        //
        public virtual void WriteLine(object? value)
        {
            if (value == null)
            {
                WriteLine();
            }
            else
            {
                // Call WriteLine(value.ToString), not Write(Object), WriteLine().
                // This makes calls to WriteLine(Object) atomic.
                if (value is IFormattable f)
                {
                    WriteLine(f.ToString(null, FormatProvider));
                }
                else
                {
                    WriteLine(value.ToString());
                }
            }
        }

        // Writes out a formatted string and a new line.  Uses the same
        // semantics as string.Format.
        //
        public virtual void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0)
        {
            WriteLine(string.Format(FormatProvider, format, arg0));
        }

        // Writes out a formatted string and a new line.  Uses the same
        // semantics as string.Format.
        //
        public virtual void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1)
        {
            WriteLine(string.Format(FormatProvider, format, arg0, arg1));
        }

        // Writes out a formatted string and a new line.  Uses the same
        // semantics as string.Format.
        //
        public virtual void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2)
        {
            WriteLine(string.Format(FormatProvider, format, arg0, arg1, arg2));
        }

        // Writes out a formatted string and a new line.  Uses the same
        // semantics as string.Format.
        //
        public virtual void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg)
        {
            WriteLine(string.Format(FormatProvider, format, arg));
        }

        /// <summary>
        /// Writes out a formatted string and a new line to the text stream, using the same semantics as <see cref="string.Format(string, ReadOnlySpan{object?})"/>.
        /// </summary>
        /// <param name="format">A composite format string.</param>
        /// <param name="arg">An object span that contains zero or more objects to format and write.</param>
        public virtual void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg)
        {
            WriteLine(string.Format(FormatProvider, format, arg));
        }

        #region Task based Async APIs
        public virtual Task WriteAsync(char value) =>
            Task.Factory.StartNew(static state =>
            {
                var t = (TupleSlim<TextWriter, char>)state!;
                t.Item1.Write(t.Item2);
            }, new TupleSlim<TextWriter, char>(this, value), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        /// <summary>
        /// Writes a rune to the text stream asynchronously.
        /// </summary>
        /// <param name="value">The rune to write to the text stream.</param>
        /// <returns>A task that represents the asynchronous write operation.</returns>
        public virtual Task WriteAsync(Rune value)
        {
            ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);

            if (valueChars.Length > 1)
            {
                return Task.Factory.StartNew(static state =>
                {
                    var t = (TupleSlim<TextWriter, char, char>)state!;
                    t.Item1.Write(t.Item2);
                    t.Item1.Write(t.Item3);
                }, new TupleSlim<TextWriter, char, char>(this, valueChars[0], valueChars[1]), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
            }

            return WriteAsync(valueChars[0]);
        }

        public virtual Task WriteAsync(string? value) =>
            Task.Factory.StartNew(static state =>
            {
                var t = (TupleSlim<TextWriter, string?>)state!;
                t.Item1.Write(t.Item2);
            }, new TupleSlim<TextWriter, string?>(this, value), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        /// <summary>
        /// Equivalent to WriteAsync(stringBuilder.ToString()) however it uses the
        /// StringBuilder.GetChunks() method to avoid creating the intermediate string
        /// </summary>
        /// <param name="value">The string (as a StringBuilder) to write to the stream</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        public virtual Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default)
        {
            return
                cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
                value == null ? Task.CompletedTask :
                WriteAsyncCore(value, cancellationToken);

            async Task WriteAsyncCore(StringBuilder sb, CancellationToken ct)
            {
                foreach (ReadOnlyMemory<char> chunk in sb.GetChunks())
                {
                    await WriteAsync(chunk, ct).ConfigureAwait(false);
                }
            }
        }

        public Task WriteAsync(char[]? buffer)
        {
            if (buffer == null)
            {
                return Task.CompletedTask;
            }

            return WriteAsync(buffer, 0, buffer.Length);
        }

        public virtual Task WriteAsync(char[] buffer, int index, int count) =>
            Task.Factory.StartNew(static state =>
            {
                var t = (TupleSlim<TextWriter, char[], int, int>)state!;
                t.Item1.Write(t.Item2, t.Item3, t.Item4);
            }, new TupleSlim<TextWriter, char[], int, int>(this, buffer, index, count), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        public virtual Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) =>
            cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
            MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
                WriteAsync(array.Array!, array.Offset, array.Count) :
                Task.Factory.StartNew(static state =>
                {
                    var t = (TupleSlim<TextWriter, ReadOnlyMemory<char>>)state!;
                    t.Item1.Write(t.Item2.Span);
                }, new TupleSlim<TextWriter, ReadOnlyMemory<char>>(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        public virtual Task WriteLineAsync(char value) =>
            Task.Factory.StartNew(static state =>
            {
                var t = (TupleSlim<TextWriter, char>)state!;
                t.Item1.WriteLine(t.Item2);
            }, new TupleSlim<TextWriter, char>(this, value), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        /// <summary>
        /// Writes a rune followed by a line terminator to the text stream asynchronously.
        /// </summary>
        /// <param name="value">The rune to write to the text stream.</param>
        /// <returns>A task that represents the asynchronous write operation.</returns>
        public virtual Task WriteLineAsync(Rune value)
        {
            ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);

            if (valueChars.Length > 1)
            {
                return Task.Factory.StartNew(static state =>
                {
                    var t = (TupleSlim<TextWriter, char, char>)state!;
                    t.Item1.Write(t.Item2);
                    t.Item1.WriteLine(t.Item3);
                }, new TupleSlim<TextWriter, char, char>(this, valueChars[0], valueChars[1]), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
            }

            return WriteLineAsync(valueChars[0]);
        }

        public virtual Task WriteLineAsync(string? value) =>
            Task.Factory.StartNew(static state =>
            {
                var t = (TupleSlim<TextWriter, string?>)state!;
                t.Item1.WriteLine(t.Item2);
            }, new TupleSlim<TextWriter, string?>(this, value), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        /// <summary>
        /// Equivalent to WriteLineAsync(stringBuilder.ToString()) however it uses the
        /// StringBuilder.GetChunks() method to avoid creating the intermediate string
        /// </summary>
        /// <param name="value">The string (as a StringBuilder) to write to the stream</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        public virtual Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default)
        {
            return
                cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
                value == null ? WriteAsync(CoreNewLine, cancellationToken) :
                WriteLineAsyncCore(value, cancellationToken);

            async Task WriteLineAsyncCore(StringBuilder sb, CancellationToken ct)
            {
                foreach (ReadOnlyMemory<char> chunk in sb.GetChunks())
                {
                    await WriteAsync(chunk, ct).ConfigureAwait(false);
                }
                await WriteAsync(CoreNewLine, ct).ConfigureAwait(false);
            }
        }

        public Task WriteLineAsync(char[]? buffer)
        {
            if (buffer == null)
            {
                return WriteLineAsync();
            }

            return WriteLineAsync(buffer, 0, buffer.Length);
        }

        public virtual Task WriteLineAsync(char[] buffer, int index, int count) =>
            Task.Factory.StartNew(static state =>
            {
                var t = (TupleSlim<TextWriter, char[], int, int>)state!;
                t.Item1.WriteLine(t.Item2, t.Item3, t.Item4);
            }, new TupleSlim<TextWriter, char[], int, int>(this, buffer, index, count), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        public virtual Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) =>
            cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
            MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ?
                WriteLineAsync(array.Array!, array.Offset, array.Count) :
                Task.Factory.StartNew(static state =>
                {
                    var t = (TupleSlim<TextWriter, ReadOnlyMemory<char>>)state!;
                    t.Item1.WriteLine(t.Item2.Span);
                }, new TupleSlim<TextWriter, ReadOnlyMemory<char>>(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

        public virtual Task WriteLineAsync()
        {
            return WriteAsync(CoreNewLine);
        }

        public virtual Task FlushAsync()
        {
            return Task.Factory.StartNew(static state => ((TextWriter)state!).Flush(), this,
                CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
        }

        /// <summary>
        /// Asynchronously clears all buffers for the current writer and causes any buffered data to
        /// be written to the underlying device.
        /// </summary>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
        /// <returns>A <see cref="Task"/> that represents the asynchronous flush operation.</returns>
        /// <exception cref="ObjectDisposedException">The text writer is disposed.</exception>
        /// <exception cref="InvalidOperationException">The writer is currently in use by a previous write operation.</exception>
        public virtual Task FlushAsync(CancellationToken cancellationToken) =>
            cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
            FlushAsync();
        #endregion

        private sealed class NullTextWriter : TextWriter
        {
            internal NullTextWriter() { }

            public override IFormatProvider FormatProvider => CultureInfo.InvariantCulture;
            public override Encoding Encoding => Encoding.Unicode;
            [AllowNull]
            public override string NewLine { get => base.NewLine; set { } }

            // To avoid all unnecessary overhead in the base, override all Flush/Write methods as pure nops.

            public override void Flush() { }
            public override Task FlushAsync() => Task.CompletedTask;
            public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;

            public override void Write(char value) { }
            public override void Write(Rune value) { }
            public override void Write(char[]? buffer) { }
            public override void Write(char[] buffer, int index, int count) { }
            public override void Write(ReadOnlySpan<char> buffer) { }
            public override void Write(bool value) { }
            public override void Write(int value) { }
            public override void Write(uint value) { }
            public override void Write(long value) { }
            public override void Write(ulong value) { }
            public override void Write(float value) { }
            public override void Write(double value) { }
            public override void Write(decimal value) { }
            public override void Write(string? value) { }
            public override void Write(object? value) { }
            public override void Write(StringBuilder? value) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) { }
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg) { }
            public override Task WriteAsync(char value) => Task.CompletedTask;
            public override Task WriteAsync(Rune value) => Task.CompletedTask;
            public override Task WriteAsync(string? value) => Task.CompletedTask;
            public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override Task WriteAsync(char[] buffer, int index, int count) => Task.CompletedTask;
            public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override void WriteLine() { }
            public override void WriteLine(char value) { }
            public override void WriteLine(Rune value) { }
            public override void WriteLine(char[]? buffer) { }
            public override void WriteLine(char[] buffer, int index, int count) { }
            public override void WriteLine(ReadOnlySpan<char> buffer) { }
            public override void WriteLine(bool value) { }
            public override void WriteLine(int value) { }
            public override void WriteLine(uint value) { }
            public override void WriteLine(long value) { }
            public override void WriteLine(ulong value) { }
            public override void WriteLine(float value) { }
            public override void WriteLine(double value) { }
            public override void WriteLine(decimal value) { }
            public override void WriteLine(string? value) { }
            public override void WriteLine(StringBuilder? value) { }
            public override void WriteLine(object? value) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) { }
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg) { }
            public override Task WriteLineAsync(char value) => Task.CompletedTask;
            public override Task WriteLineAsync(Rune value) => Task.CompletedTask;
            public override Task WriteLineAsync(string? value) => Task.CompletedTask;
            public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override Task WriteLineAsync(char[] buffer, int index, int count) => Task.CompletedTask;
            public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => Task.CompletedTask;
            public override Task WriteLineAsync() => Task.CompletedTask;
        }

        public static TextWriter Synchronized(TextWriter writer)
        {
            ArgumentNullException.ThrowIfNull(writer);

#if (!TARGET_BROWSER && !TARGET_WASI) || FEATURE_WASM_MANAGED_THREADS
            return writer is SyncTextWriter ? writer : new SyncTextWriter(writer);
#else
            return writer;
#endif
        }

        internal sealed class SyncTextWriter : TextWriter, IDisposable
        {
            private readonly TextWriter _out;

            internal SyncTextWriter(TextWriter t)
            {
                _out = t;
            }

            public override Encoding Encoding => _out.Encoding;

            public override IFormatProvider FormatProvider => _out.FormatProvider;

            [AllowNull]
            public override string NewLine
            {
                [MethodImpl(MethodImplOptions.Synchronized)]
                get => _out.NewLine;
                [MethodImpl(MethodImplOptions.Synchronized)]
                set => _out.NewLine = value;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Close() => _out.Close();

            [MethodImpl(MethodImplOptions.Synchronized)]
            protected override void Dispose(bool disposing)
            {
                // Explicitly pick up a potentially methodimpl'ed Dispose
                if (disposing)
                    ((IDisposable)_out).Dispose();
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Flush() => _out.Flush();

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(char value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(Rune value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(char[]? buffer) => _out.Write(buffer);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(char[] buffer, int index, int count) => _out.Write(buffer, index, count);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(ReadOnlySpan<char> buffer) => _out.Write(buffer);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(bool value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(int value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(uint value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(long value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(ulong value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(float value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(double value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(decimal value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(string? value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(StringBuilder? value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write(object? value) => _out.Write(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) => _out.Write(format, arg0);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) => _out.Write(format, arg0, arg1);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) => _out.Write(format, arg0, arg1, arg2);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object?[] arg) => _out.Write(format, arg);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg) => _out.Write(format, arg);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine() => _out.WriteLine();

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(char value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(Rune value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(decimal value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(char[]? buffer) => _out.WriteLine(buffer);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(char[] buffer, int index, int count) => _out.WriteLine(buffer, index, count);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(ReadOnlySpan<char> buffer) => _out.WriteLine(buffer);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(bool value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(int value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(uint value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(long value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(ulong value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(float value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(double value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(string? value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(StringBuilder? value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine(object? value) => _out.WriteLine(value);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) => _out.WriteLine(format, arg0);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) => _out.WriteLine(format, arg0, arg1);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) => _out.WriteLine(format, arg0, arg1, arg2);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object?[] arg) => _out.WriteLine(format, arg);

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg) => _out.WriteLine(format, arg);

            //
            // On SyncTextWriter all APIs should run synchronously, even the async ones.
            //

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override ValueTask DisposeAsync()
            {
                Dispose();
                return default;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteAsync(char value)
            {
                Write(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteAsync(Rune value)
            {
                Write(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteAsync(string? value)
            {
                Write(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return Task.FromCanceled(cancellationToken);
                }

                Write(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteAsync(char[] buffer, int index, int count)
            {
                Write(buffer, index, count);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return Task.FromCanceled(cancellationToken);
                }

                Write(buffer.Span);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return Task.FromCanceled(cancellationToken);
                }

                WriteLine(buffer.Span);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync(char value)
            {
                WriteLine(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync(Rune value)
            {
                WriteLine(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync()
            {
                WriteLine();
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync(string? value)
            {
                WriteLine(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return Task.FromCanceled(cancellationToken);
                }

                WriteLine(value);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task WriteLineAsync(char[] buffer, int index, int count)
            {
                WriteLine(buffer, index, count);
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task FlushAsync()
            {
                Flush();
                return Task.CompletedTask;
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public override Task FlushAsync(CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return Task.FromCanceled(cancellationToken);
                }

                Flush();
                return Task.CompletedTask;
            }
        }
    }
}
